inquirer 9.x.x → 10.x.x へマイグレーションしてみた

inquirer 9.x.x → 10.x.x へマイグレーションしてみた

Clock Icon2024.08.23

はじめに

こんにちは。アノテーションの及川です。

今回はライブラリアップデートのときにメジャーアップデートを確認(inquirer 9.x.xinquirer 10.x.x)したので、このアップデートに対してどのように修正対応したかをまとめていきます。

inquirer(Inquirer.js) とは

inquirer は、Node.js で動作するコマンドラインインターフェース(CLI)を作成するためのライブラリです。

以下が inquirer の主な特徴です。

1. ユーザー入力の収集

テキスト入力、パスワード、選択肢からの選択、確認(Yes/No)など、様々な形式でユーザーからの入力を簡単に収集できます。

2. 豊富な入力タイプ

・通常のテキスト入力
・パスワード(マスク付き)入力
・数値入力
・リストからの選択
・チェックボックス(複数選択)
・ラジオボタン(単一選択)
・確認(Yes/No) など
・入力の検証: ユーザーの入力に対して、カスタムの検証ルールを設定できます。

3. 入力の検証

ユーザーの入力に対して、カスタムの検証ルールを設定できます。

他の詳細については下記ページをご参照ください。

https://github.com/SBoudrias/Inquirer.js/tree/main

https://github.com/SBoudrias/Inquirer.js/blob/main/packages/inquirer/README.md#documentation

調査

9.x.x から 10.x.x へのマイグレーション方法について下記の観点で調査しました。

npm - Inquirer.js

https://www.npmjs.com/package/inquirer

A collection of common interactive command line user interfaces.
[!IMPORTANT] This is the legacy version of Inquirer.js. While it still receives maintenance, it is not actively developed. For the new Inquirer, see @inquirer/prompts.

■ざっくり要約

  • Inquirer.js の旧バージョンは今でもメンテナンスは受けているが、積極的には開発されていないこと
  • 新しい Inquirer については、@inquirer/prompts を参照することを案内していること

GitHubリポジトリのReleasesの確認

https://github.com/SBoudrias/Inquirer.js/releases/tag/inquirer%4010.0.0

Custom prompts built on [email protected] will keep working, but should plan a migration.
inquirer.ui.BottomBar is deleted.

■ざっくり要約

  • Inquirer 9.x.x 向けに作成されたカスタムプロンプトは引き続き動作しますが、将来的な移行を計画すべきことが案内されていること

使用方法の確認

https://github.com/SBoudrias/Inquirer.js/tree/main/packages

■ざっくり要約

  • 各形式に対しての使用方法の例が記載されている
  • 例えば複数の選択肢のリストから 単一選択を表示するためのコマンドラインプロンプト(@inquirer/select)については、下記ページに記載されている

https://github.com/SBoudrias/Inquirer.js/tree/main/packages/select

検証

実際に 9.x.x 系のバージョンでコードを書いた後に、10.x.x 系での書き方に修正して動作を確認してみました。

  • 9.x.x 系: 9.3.5 を npm インストールしての検証
  • 10.x.x 系: 10.1.8 を npm インストールしての検証

また、今回のユーザーからの入力形式はリストからの選択確認(Yes/No通常のテキスト入力について試しました。

バージョン リストからの選択 確認(Yes/No) 通常のテキスト入力 今回使用バージョン
9.x.x 系 list confirm input 9.3.5
10.x.x 系 select confirm input 10.1.8

inquirer 9.3.5 での記述例

index.ts
import inquirer from "inquirer";

const tasks = ["Hello, Worldを出力する", "ユーザー入力を表示する"] as const;

type Task = (typeof tasks)[number];

const main = async (): Promise<void> => {

  const name = "タスクを選択してください";
  const taskAnswers = await inquirer.prompt([
    {
      type: "list",
      name,
      choices: tasks,
    },
  ]);

  const task: Task = taskAnswers[name];

  const confirmAnswer = await inquirer.prompt({
    type: "confirm",
    name: "confirm",
    message: "実行しますか?",
  });

  if (confirmAnswer.confirm === false) {
    console.log("キャンセルしました。");
    return;
  }

  switch (task) {
    case "Hello, World!を出力する":
      await taskHelloWorld();
      break;
    case "ユーザー入力を表示する":
      await taskUserInput();
      break;
    default: {
      throw new Error(`Invalid task. task: ${task}`);
    }
  }
};

const taskHelloWorld = async (): Promise<void> => {
  console.log("Hello, World!");
};

const taskUserInput = async (): Promise<void> => {
  const userInput = await inquirer.prompt({
    type: "input",
    name: "input",
    message: "何か入力してください:",
  });
  console.log(`ユーザーの入力: ${userInput.input}`);
};

main();
実行結果(inquirer 9.3.5)
% npx tsx src/index.ts
? タスクを選択してください: Hello, Worldを出力する!
? 実行しますか? Yes
Hello, World!

% npx tsx src/index.ts
? タスクを選択してください: ユーザー入力を表示する
? 実行しますか? Yes
? 何か入力してください: inquirer 9.3.5 の場合の  Test 入力です!
ユーザーの入力: inquirer 9.3.5 の場合の Test 入力です!

inquirer 10.1.8 での記述例

index.ts
import { input, select, confirm } from "@inquirer/prompts";

const tasks = ["Hello, Worldを出力する!", "ユーザー入力を表示する"] as const;

type Task = (typeof tasks)[number];

const main = async (): Promise<void> => {
  const taskAnswers: Task = await select({
    message: "タスクを選択してください。",
    choices: tasks.map((task) => ({
      value: task,
    })),
  });

  const task: Task = taskAnswers as Task;

  const confirmAnswer = await confirm({
    message: "実行しますか?",
  });

  if (confirmAnswer === false) {
    console.log("キャンセルしました。");
    return;
  }

  switch (task) {
    case "Hello, Worldを出力する!":
      await taskHelloWorld();
      break;
    case "ユーザー入力を表示する":
      await taskUserInput();
      break;
    default: {
      throw new Error(`Invalid task. task: ${task}`);
    }
  }
};

const taskHelloWorld = async (): Promise<void> => {
  console.log("Hello, World!");
};

const taskUserInput = async (): Promise<void> => {
  const userInput = await input({
    message: "何か入力してください:",
  });
  console.log(`ユーザーの入力: ${userInput}`);
};

main();
実行結果(inquirer 10.1.8)
% npx tsx src/index.ts
? タスクを選択してください。 Hello, Worldを出力する!
? 実行しますか? yes
Hello, World!

% npx tsx src/index.ts
? タスクを選択してください。 ユーザー入力を表示する
? 実行しますか? yes
? 何か入力してください: inquirer 10.1.8 の場合の Test 入力です!
ユーザーの入力: inquirer 10.1.8 の場合の Test 入力です!

(参考)差分表示

差分表示(diff)は下記となります。

index.ts
+ import { input, select, confirm } from "@inquirer/prompts";
- import inquirer from "inquirer";

const tasks = ["Hello, Worldを出力する!", "ユーザー入力を表示する"] as const;

type Task = (typeof tasks)[number];

const main = async (): Promise<void> => {
+  const taskAnswers: Task = await select({
+    message: "タスクを選択してください。",
+    choices: tasks.map((task) => ({
+      value: task,
+    })),
+  });
-  const name = `タスクを選択してください`;
-  const taskAnswers = await inquirer.prompt([
-    {
-      type: "list",
-      name,
-      choices: tasks,
-    },
-  ]);

+  const task: Task = taskAnswers as Task;
-  const task: Task = taskAnswers[name];

+  const confirmAnswer = await confirm({
+    message: "実行しますか?",
+  });
-  const confirmAnswer = await inquirer.prompt({
-    type: "confirm",
-    name: "confirm",
-    message: "実行しますか?",
-  });

+  if (confirmAnswer === false) {
+    console.log("キャンセルしました。");
+    return;
+  }
-  if (confirmAnswer.confirm === false) {
-    console.log("キャンセルしました。");
-    return;
-  }

  switch (task) {
    case "Hello, Worldを出力する!":
      await taskHelloWorld();
      break;
    case "ユーザー入力を表示する":
      await taskUserInput();
      break;
    default: {
      throw new Error(`Invalid task. task: ${task}`);
    }
  }
};

const taskHelloWorld = async (): Promise<void> => {
  console.log("Hello, World!");
};

const taskUserInput = async (): Promise<void> => {
+  const userInput = await input({
+    message: "何か入力してください:",
+  });
+  console.log(`ユーザーの入力: ${userInput}`);
-  const userInput = await inquirer.prompt({
-    type: "input",
-    name: "input",
-    message: "何か入力してください:",
-  });
-  console.log(`ユーザーの入力: ${userInput.input}`);
};

main();

補足

TypeScript の型定義が変更(再実装)になったことが関連しているかは不明ですが、メジャーバージョンアップしたことで、下記にようにエラーが表示されることも確認しましたので参考情報として示します。

const userInput = await inquirer.prompt({
                        ^^^^^^^^ // ← `名前 'inquirer' が見つかりません。`とエラーが表示されます
  type: "input",
  name: "input",
  message: "何か入力してください:",
});

https://github.com/SBoudrias/Inquirer.js/releases/tag/inquirer%4010.0.0

Re-implemented with Typescript.

まとめ

inquirer 9.x.x(今回: 9.3.5) → 10.x.x(今回: 10.1.8) への移行(マイグレーション)に関しては下記の観点より、以前の記述方法よりもシンプルにコーディングできるものと思いました。

  • 変更前では、type 属性を使用して入力の種別(例:input、confirm など)を指定していましたが
    変更後では、前述のコード例のように inquirer の各種別に対応する関数(例:input、confirm)を直接インポートして使用することができること
  • 変更前では、name 属性を使用して定義したフィールドについて、戻り値のオブジェクトのプロパティとして返されていましたが変更後では、関数の戻り値が直接そのまま返されること

この記事が誰かのお役に立てば幸いです。

アノテーション株式会社について

アノテーション株式会社はクラスメソッドグループのオペレーション専門特化企業です。サポート・運用・開発保守・情シス・バックオフィスの専門チームが、最新 IT テクノロジー、高い技術力、蓄積されたノウハウをフル活用し、お客様の課題解決を行っています。当社は様々な職種でメンバーを募集しています。「オペレーション・エクセレンス」と「らしく働く、らしく生きる」を共に実現するカルチャー・しくみ・働き方にご興味がある方は、アノテーション株式会社 採用サイトをぜひご覧ください。

参考資料

Share this article

facebook logohatena logotwitter logo

© Classmethod, Inc. All rights reserved.